// SPDX-License-Identifier: GPL-2.0
/* Copyright( c ) 2009-2012  Realtek Corporation.*/

#include "wifi.h"
#include "cam.h"

#include <linux/moduleparam.h>
#include <linux/vmalloc.h>

#ifdef CONFIG_RTLWIFI_DEBUG
void _rtl_dbg_trace( struct rtl_priv *rtlpriv, u64 comp, int level,
		    const char *fmt, ... )
{
	if ( unlikely( ( comp & rtlpriv->cfg->mod_params->debug_mask ) &&
		     level <= rtlpriv->cfg->mod_params->debug_level ) ) {
		struct va_format vaf;
		va_list args;

		va_start( args, fmt );

		vaf.fmt = fmt;
		vaf.va = &args;

		pr_info( ":<%lx> %pV", in_interrupt(), &vaf );

		va_end( args );
	}
}
EXPORT_SYMBOL_GPL( _rtl_dbg_trace );

void _rtl_dbg_print( struct rtl_priv *rtlpriv, u64 comp, int level,
		    const char *fmt, ... )
{
	if ( unlikely( ( comp & rtlpriv->cfg->mod_params->debug_mask ) &&
		     level <= rtlpriv->cfg->mod_params->debug_level ) ) {
		struct va_format vaf;
		va_list args;

		va_start( args, fmt );

		vaf.fmt = fmt;
		vaf.va = &args;

		pr_info( "%pV", &vaf );

		va_end( args );
	}
}
EXPORT_SYMBOL_GPL( _rtl_dbg_print );

void _rtl_dbg_print_data( struct rtl_priv *rtlpriv, u64 comp, int level,
			 const char *titlestring,
			 const void *hexdata, int hexdatalen )
{
	if ( unlikely( ( ( comp ) & rtlpriv->cfg->mod_params->debug_mask ) &&
		     ( ( level ) <= rtlpriv->cfg->mod_params->debug_level ) ) ) {
		pr_info( "In process \"%s\" (pid %i): %s\n",
			current->comm, current->pid, titlestring );
		print_hex_dump_bytes( "", DUMP_PREFIX_NONE,
				     hexdata, hexdatalen );
	}
}
EXPORT_SYMBOL_GPL( _rtl_dbg_print_data );

struct rtl_debugfs_priv {
	struct rtl_priv *rtlpriv;
	int ( *cb_read )( struct seq_file *m, void *v );
	ssize_t ( *cb_write )( struct file *filp, const char __user *buffer,
			    size_t count, loff_t *loff );
	u32 cb_data;
};

static struct dentry *debugfs_topdir;

static int rtl_debug_get_common( struct seq_file *m, void *v )
{
	struct rtl_debugfs_priv *debugfs_priv = m->private;

	return debugfs_priv->cb_read( m, v );
}

static int dl_debug_open_common( struct inode *inode, struct file *file )
{
	return single_open( file, rtl_debug_get_common, inode->i_private );
}

static const struct file_operations file_ops_common = {
	.open = dl_debug_open_common,
	.read = seq_read,
	.llseek = seq_lseek,
	.release = single_release,
};

static int rtl_debug_get_mac_page( struct seq_file *m, void *v )
{
	struct rtl_debugfs_priv *debugfs_priv = m->private;
	struct rtl_priv *rtlpriv = debugfs_priv->rtlpriv;
	u32 page = debugfs_priv->cb_data;
	int i, n;
	int max = 0xff;

	for ( n = 0; n <= max; ) {
		seq_printf( m, "\n%8.8x  ", n + page );
		for ( i = 0; i < 4 && n <= max; i++, n += 4 )
			seq_printf( m, "%8.8x    ",
				   rtl_read_dword( rtlpriv, ( page | n ) ) );
	}
	seq_puts( m, "\n" );
	return 0;
}

#define RTL_DEBUG_IMPL_MAC_SERIES( page, addr )			\
static struct rtl_debugfs_priv rtl_debug_priv_mac_ ##page = {	\
	.cb_read = rtl_debug_get_mac_page,			\
	.cb_data = addr,					\
}

RTL_DEBUG_IMPL_MAC_SERIES( 0, 0x0000 );
RTL_DEBUG_IMPL_MAC_SERIES( 1, 0x0100 );
RTL_DEBUG_IMPL_MAC_SERIES( 2, 0x0200 );
RTL_DEBUG_IMPL_MAC_SERIES( 3, 0x0300 );
RTL_DEBUG_IMPL_MAC_SERIES( 4, 0x0400 );
RTL_DEBUG_IMPL_MAC_SERIES( 5, 0x0500 );
RTL_DEBUG_IMPL_MAC_SERIES( 6, 0x0600 );
RTL_DEBUG_IMPL_MAC_SERIES( 7, 0x0700 );
RTL_DEBUG_IMPL_MAC_SERIES( 10, 0x1000 );
RTL_DEBUG_IMPL_MAC_SERIES( 11, 0x1100 );
RTL_DEBUG_IMPL_MAC_SERIES( 12, 0x1200 );
RTL_DEBUG_IMPL_MAC_SERIES( 13, 0x1300 );
RTL_DEBUG_IMPL_MAC_SERIES( 14, 0x1400 );
RTL_DEBUG_IMPL_MAC_SERIES( 15, 0x1500 );
RTL_DEBUG_IMPL_MAC_SERIES( 16, 0x1600 );
RTL_DEBUG_IMPL_MAC_SERIES( 17, 0x1700 );

static int rtl_debug_get_bb_page( struct seq_file *m, void *v )
{
	struct rtl_debugfs_priv *debugfs_priv = m->private;
	struct rtl_priv *rtlpriv = debugfs_priv->rtlpriv;
	struct ieee80211_hw *hw = rtlpriv->hw;
	u32 page = debugfs_priv->cb_data;
	int i, n;
	int max = 0xff;

	for ( n = 0; n <= max; ) {
		seq_printf( m, "\n%8.8x  ", n + page );
		for ( i = 0; i < 4 && n <= max; i++, n += 4 )
			seq_printf( m, "%8.8x    ",
				   rtl_get_bbreg( hw, ( page | n ), 0xffffffff ) );
	}
	seq_puts( m, "\n" );
	return 0;
}

#define RTL_DEBUG_IMPL_BB_SERIES( page, addr )			\
static struct rtl_debugfs_priv rtl_debug_priv_bb_ ##page = {	\
	.cb_read = rtl_debug_get_bb_page,			\
	.cb_data = addr,					\
}

RTL_DEBUG_IMPL_BB_SERIES( 8, 0x0800 );
RTL_DEBUG_IMPL_BB_SERIES( 9, 0x0900 );
RTL_DEBUG_IMPL_BB_SERIES( a, 0x0a00 );
RTL_DEBUG_IMPL_BB_SERIES( b, 0x0b00 );
RTL_DEBUG_IMPL_BB_SERIES( c, 0x0c00 );
RTL_DEBUG_IMPL_BB_SERIES( d, 0x0d00 );
RTL_DEBUG_IMPL_BB_SERIES( e, 0x0e00 );
RTL_DEBUG_IMPL_BB_SERIES( f, 0x0f00 );
RTL_DEBUG_IMPL_BB_SERIES( 18, 0x1800 );
RTL_DEBUG_IMPL_BB_SERIES( 19, 0x1900 );
RTL_DEBUG_IMPL_BB_SERIES( 1a, 0x1a00 );
RTL_DEBUG_IMPL_BB_SERIES( 1b, 0x1b00 );
RTL_DEBUG_IMPL_BB_SERIES( 1c, 0x1c00 );
RTL_DEBUG_IMPL_BB_SERIES( 1d, 0x1d00 );
RTL_DEBUG_IMPL_BB_SERIES( 1e, 0x1e00 );
RTL_DEBUG_IMPL_BB_SERIES( 1f, 0x1f00 );

static int rtl_debug_get_reg_rf( struct seq_file *m, void *v )
{
	struct rtl_debugfs_priv *debugfs_priv = m->private;
	struct rtl_priv *rtlpriv = debugfs_priv->rtlpriv;
	struct ieee80211_hw *hw = rtlpriv->hw;
	enum radio_path rfpath = debugfs_priv->cb_data;
	int i, n;
	int max = 0x40;

	if ( IS_HARDWARE_TYPE_8822B( rtlpriv ) )
		max = 0xff;

	seq_printf( m, "\nPATH(%d)", rfpath );

	for ( n = 0; n <= max; ) {
		seq_printf( m, "\n%8.8x  ", n );
		for ( i = 0; i < 4 && n <= max; n += 1, i++ )
			seq_printf( m, "%8.8x    ",
				   rtl_get_rfreg( hw, rfpath, n, 0xffffffff ) );
	}
	seq_puts( m, "\n" );
	return 0;
}

#define RTL_DEBUG_IMPL_RF_SERIES( page, addr )			\
static struct rtl_debugfs_priv rtl_debug_priv_rf_ ##page = {	\
	.cb_read = rtl_debug_get_reg_rf,			\
	.cb_data = addr,					\
}

RTL_DEBUG_IMPL_RF_SERIES( a, RF90_PATH_A );
RTL_DEBUG_IMPL_RF_SERIES( b, RF90_PATH_B );

static int rtl_debug_get_cam_register( struct seq_file *m, void *v )
{
	struct rtl_debugfs_priv *debugfs_priv = m->private;
	struct rtl_priv *rtlpriv = debugfs_priv->rtlpriv;
	int start = debugfs_priv->cb_data;
	u32 target_cmd = 0;
	u32 target_val = 0;
	u8 entry_i = 0;
	u32 ulstatus;
	int i = 100, j = 0;
	int end = ( start + 11 > TOTAL_CAM_ENTRY ? TOTAL_CAM_ENTRY : start + 11 );

	/* This dump the current register page */
	seq_printf( m,
		   "\n#################### SECURITY CAM (%d-%d) ##################\n",
		   start, end - 1 );

	for ( j = start; j < end; j++ ) {
		seq_printf( m, "\nD:  %2x > ", j );
		for ( entry_i = 0; entry_i < CAM_CONTENT_COUNT; entry_i++ ) {
			/* polling bit, and No Write enable, and address  */
			target_cmd = entry_i + CAM_CONTENT_COUNT * j;
			target_cmd = target_cmd | BIT( 31 );

			/* Check polling bit is clear */
			while ( ( i-- ) >= 0 ) {
				ulstatus =
				    rtl_read_dword( rtlpriv,
						   rtlpriv->cfg->maps[RWCAM] );
				if ( ulstatus & BIT( 31 ) )
					continue;
				else
					break;
			}

			rtl_write_dword( rtlpriv, rtlpriv->cfg->maps[RWCAM],
					target_cmd );
			target_val = rtl_read_dword( rtlpriv,
						    rtlpriv->cfg->maps[RCAMO] );
			seq_printf( m, "%8.8x ", target_val );
		}
	}
	seq_puts( m, "\n" );
	return 0;
}

#define RTL_DEBUG_IMPL_CAM_SERIES( page, addr )			\
static struct rtl_debugfs_priv rtl_debug_priv_cam_ ##page = {	\
	.cb_read = rtl_debug_get_cam_register,			\
	.cb_data = addr,					\
}

RTL_DEBUG_IMPL_CAM_SERIES( 1, 0 );
RTL_DEBUG_IMPL_CAM_SERIES( 2, 11 );
RTL_DEBUG_IMPL_CAM_SERIES( 3, 22 );

static int rtl_debug_get_btcoex( struct seq_file *m, void *v )
{
	struct rtl_debugfs_priv *debugfs_priv = m->private;
	struct rtl_priv *rtlpriv = debugfs_priv->rtlpriv;

	if ( rtlpriv->cfg->ops->get_btc_status() )
		rtlpriv->btcoexist.btc_ops->btc_display_bt_coex_info( rtlpriv,
								     m );

	seq_puts( m, "\n" );

	return 0;
}

static struct rtl_debugfs_priv rtl_debug_priv_btcoex = {
	.cb_read = rtl_debug_get_btcoex,
	.cb_data = 0,
};

static ssize_t rtl_debugfs_set_write_reg( struct file *filp,
					 const char __user *buffer,
					 size_t count, loff_t *loff )
{
	struct rtl_debugfs_priv *debugfs_priv = filp->private_data;
	struct rtl_priv *rtlpriv = debugfs_priv->rtlpriv;
	char tmp[32 + 1];
	int tmp_len;
	u32 addr, val, len;
	int num;

	if ( count < 3 )
		return -EFAULT;

	tmp_len = ( count > sizeof( tmp ) - 1 ? sizeof( tmp ) - 1 : count );

	if ( !buffer || copy_from_user( tmp, buffer, tmp_len ) )
		return count;

	tmp[tmp_len] = '\0';

	/* write BB/MAC register */
	num = sscanf( tmp, "%x %x %x", &addr, &val, &len );

	if ( num !=  3 )
		return count;

	switch ( len ) {
	case 1:
		rtl_write_byte( rtlpriv, addr, ( u8 )val );
		break;
	case 2:
		rtl_write_word( rtlpriv, addr, ( u16 )val );
		break;
	case 4:
		rtl_write_dword( rtlpriv, addr, val );
		break;
	default:
		/*printk( "error write length=%d", len );*/
		break;
	}

	return count;
}

static struct rtl_debugfs_priv rtl_debug_priv_write_reg = {
	.cb_write = rtl_debugfs_set_write_reg,
};

static ssize_t rtl_debugfs_set_write_h2c( struct file *filp,
					 const char __user *buffer,
					 size_t count, loff_t *loff )
{
	struct rtl_debugfs_priv *debugfs_priv = filp->private_data;
	struct rtl_priv *rtlpriv = debugfs_priv->rtlpriv;
	struct ieee80211_hw *hw = rtlpriv->hw;
	char tmp[32 + 1];
	int tmp_len;
	u8 h2c_len, h2c_data_packed[8];
	int h2c_data[8];	/* idx 0: cmd */
	int i;

	if ( count < 3 )
		return -EFAULT;

	tmp_len = ( count > sizeof( tmp ) - 1 ? sizeof( tmp ) - 1 : count );

	if ( !buffer || copy_from_user( tmp, buffer, tmp_len ) )
		return count;

	tmp[tmp_len] = '\0';

	h2c_len = sscanf( tmp, "%X %X %X %X %X %X %X %X",
			 &h2c_data[0], &h2c_data[1],
			 &h2c_data[2], &h2c_data[3],
			 &h2c_data[4], &h2c_data[5],
			 &h2c_data[6], &h2c_data[7] );

	if ( h2c_len <= 0 )
		return count;

	for ( i = 0; i < h2c_len; i++ )
		h2c_data_packed[i] = ( u8 )h2c_data[i];

	rtlpriv->cfg->ops->fill_h2c_cmd( hw, h2c_data_packed[0],
					h2c_len - 1,
					&h2c_data_packed[1] );

	return count;
}

static struct rtl_debugfs_priv rtl_debug_priv_write_h2c = {
	.cb_write = rtl_debugfs_set_write_h2c,
};

static ssize_t rtl_debugfs_set_write_rfreg( struct file *filp,
					   const char __user *buffer,
					    size_t count, loff_t *loff )
{
	struct rtl_debugfs_priv *debugfs_priv = filp->private_data;
	struct rtl_priv *rtlpriv = debugfs_priv->rtlpriv;
	struct ieee80211_hw *hw = rtlpriv->hw;
	char tmp[32 + 1];
	int tmp_len;
	int num;
	int path;
	u32 addr, bitmask, data;

	if ( count < 3 )
		return -EFAULT;

	tmp_len = ( count > sizeof( tmp ) - 1 ? sizeof( tmp ) - 1 : count );

	if ( !buffer || copy_from_user( tmp, buffer, tmp_len ) )
		return count;

	tmp[tmp_len] = '\0';

	num = sscanf( tmp, "%X %X %X %X",
		     &path, &addr, &bitmask, &data );

	if ( num != 4 ) {
		RT_TRACE( rtlpriv, COMP_ERR, DBG_DMESG,
			 "Format is <path> <addr> <mask> <data>\n" );
		return count;
	}

	rtl_set_rfreg( hw, path, addr, bitmask, data );

	return count;
}

static struct rtl_debugfs_priv rtl_debug_priv_write_rfreg = {
	.cb_write = rtl_debugfs_set_write_rfreg,
};

static int rtl_debugfs_close( struct inode *inode, struct file *filp )
{
	return 0;
}

static ssize_t rtl_debugfs_common_write( struct file *filp,
					const char __user *buffer,
					size_t count, loff_t *loff )
{
	struct rtl_debugfs_priv *debugfs_priv = filp->private_data;

	return debugfs_priv->cb_write( filp, buffer, count, loff );
}

static const struct file_operations file_ops_common_write = {
	.owner = THIS_MODULE,
	.write = rtl_debugfs_common_write,
	.open = simple_open,
	.release = rtl_debugfs_close,
};

#define RTL_DEBUGFS_ADD_CORE( name, mode, fopname )			   \
	do {								   \
		rtl_debug_priv_ ##name.rtlpriv = rtlpriv;		   \
		debugfs_create_file( #name, mode, parent,		   \
				    &rtl_debug_priv_ ##name,		   \
				    &file_ops_ ##fopname );		   \
	} while ( 0 )

#define RTL_DEBUGFS_ADD( name )						   \
		RTL_DEBUGFS_ADD_CORE( name, S_IFREG | 0444, common )
#define RTL_DEBUGFS_ADD_W( name )						   \
		RTL_DEBUGFS_ADD_CORE( name, S_IFREG | 0222, common_write )

void rtl_debug_add_one( struct ieee80211_hw *hw )
{
	struct rtl_priv *rtlpriv = rtl_priv( hw );
	struct rtl_efuse *rtlefuse = rtl_efuse( rtl_priv( hw ) );
	struct dentry *parent;

	snprintf( rtlpriv->dbg.debugfs_name, 18, "%pMF", rtlefuse->dev_addr );

	rtlpriv->dbg.debugfs_dir =
		debugfs_create_dir( rtlpriv->dbg.debugfs_name, debugfs_topdir );

	parent = rtlpriv->dbg.debugfs_dir;

	RTL_DEBUGFS_ADD( mac_0 );
	RTL_DEBUGFS_ADD( mac_1 );
	RTL_DEBUGFS_ADD( mac_2 );
	RTL_DEBUGFS_ADD( mac_3 );
	RTL_DEBUGFS_ADD( mac_4 );
	RTL_DEBUGFS_ADD( mac_5 );
	RTL_DEBUGFS_ADD( mac_6 );
	RTL_DEBUGFS_ADD( mac_7 );
	RTL_DEBUGFS_ADD( bb_8 );
	RTL_DEBUGFS_ADD( bb_9 );
	RTL_DEBUGFS_ADD( bb_a );
	RTL_DEBUGFS_ADD( bb_b );
	RTL_DEBUGFS_ADD( bb_c );
	RTL_DEBUGFS_ADD( bb_d );
	RTL_DEBUGFS_ADD( bb_e );
	RTL_DEBUGFS_ADD( bb_f );
	RTL_DEBUGFS_ADD( mac_10 );
	RTL_DEBUGFS_ADD( mac_11 );
	RTL_DEBUGFS_ADD( mac_12 );
	RTL_DEBUGFS_ADD( mac_13 );
	RTL_DEBUGFS_ADD( mac_14 );
	RTL_DEBUGFS_ADD( mac_15 );
	RTL_DEBUGFS_ADD( mac_16 );
	RTL_DEBUGFS_ADD( mac_17 );
	RTL_DEBUGFS_ADD( bb_18 );
	RTL_DEBUGFS_ADD( bb_19 );
	RTL_DEBUGFS_ADD( bb_1a );
	RTL_DEBUGFS_ADD( bb_1b );
	RTL_DEBUGFS_ADD( bb_1c );
	RTL_DEBUGFS_ADD( bb_1d );
	RTL_DEBUGFS_ADD( bb_1e );
	RTL_DEBUGFS_ADD( bb_1f );
	RTL_DEBUGFS_ADD( rf_a );
	RTL_DEBUGFS_ADD( rf_b );

	RTL_DEBUGFS_ADD( cam_1 );
	RTL_DEBUGFS_ADD( cam_2 );
	RTL_DEBUGFS_ADD( cam_3 );

	RTL_DEBUGFS_ADD( btcoex );

	RTL_DEBUGFS_ADD_W( write_reg );
	RTL_DEBUGFS_ADD_W( write_h2c );
	RTL_DEBUGFS_ADD_W( write_rfreg );
}
EXPORT_SYMBOL_GPL( rtl_debug_add_one );

void rtl_debug_remove_one( struct ieee80211_hw *hw )
{
	struct rtl_priv *rtlpriv = rtl_priv( hw );

	debugfs_remove_recursive( rtlpriv->dbg.debugfs_dir );
	rtlpriv->dbg.debugfs_dir = NULL;
}
EXPORT_SYMBOL_GPL( rtl_debug_remove_one );

void rtl_debugfs_add_topdir( void )
{
	debugfs_topdir = debugfs_create_dir( "rtlwifi", NULL );
}

void rtl_debugfs_remove_topdir( void )
{
	debugfs_remove_recursive( debugfs_topdir );
}

#endif
